#include "stdafx.h"
#include "Packages.h"
#include "SystemVersion.h"
#include "Logger.h"

#include "Poco/MD5Engine.h"
#include "Poco/DigestStream.h"
#include "Poco/Base64Encoder.h"
#include "Poco/StreamCopier.h"
#include "Poco/File.h"

#include <fstream>
#include <iostream>

namespace ExamSystem
{
    using Poco::DigestEngine;
    using Poco::MD5Engine;
    using Poco::DigestOutputStream;
    using Poco::StreamCopier;
    using Poco::Base64Encoder;

#define ImplementProtocolString(x, y) const std::string CExamProtocol::##x = #y

    // we use empty namespace now
    ImplementProtocolString(namespaceURI, );
    const std::string CExamProtocol::systemVersion = ExamSystemVersion;

    ImplementProtocolString(labelExamination, examination);
    ImplementProtocolString(labelPackage, package);
    ImplementProtocolString(labelIdentity, identity);
    ImplementProtocolString(labelVersion, version);
    ImplementProtocolString(labelFile, file);
    ImplementProtocolString(labelMessage, message);
    ImplementProtocolString(labelQuestion, question);

    ImplementProtocolString(capsuledLabelExamination, <examination>); 
    ImplementProtocolString(capsuledLabelExaminationEnd, </examination>);

    ImplementProtocolString(attributeType, type);
    ImplementProtocolString(attributeIdentity, identity);
    ImplementProtocolString(attributeVersion, version);
    ImplementProtocolString(attributeResult, result);
    ImplementProtocolString(attributeValue, value);
    ImplementProtocolString(attributeName, name);
    ImplementProtocolString(attributeSize, size);
    ImplementProtocolString(attributeMd5, md5);
    ImplementProtocolString(attributeOrder, order);
    ImplementProtocolString(attributeRandomQuestion, randomQuestion);
    ImplementProtocolString(attributeExamTime, examTime);
    ImplementProtocolString(attributeNumber, number);

    ImplementProtocolString(typeHold, hold);
    ImplementProtocolString(typeHoldReply, holdReply);
    ImplementProtocolString(typeIdentity, identity);
    ImplementProtocolString(typeSystemVersion, systemVersion);
    ImplementProtocolString(typeVersion, version);
    ImplementProtocolString(typeVersionCheck, versionCheck);
    ImplementProtocolString(typeDatabase, database);
    ImplementProtocolString(typeReceivingResult, receivingResult);
    ImplementProtocolString(typeExamOrder, examOrder);
    ImplementProtocolString(typeExamMessage, examMessage);
    ImplementProtocolString(typeExamInformation, examInformation);

    ImplementProtocolString(valueServer, server);
    ImplementProtocolString(valueClient, client);
    ImplementProtocolString(valueOk, ok);
    ImplementProtocolString(valuePassed, passed);
    ImplementProtocolString(valueFailed, failed);
    ImplementProtocolString(valueSystem, system);
    ImplementProtocolString(valueDatabase, database);
    ImplementProtocolString(valueExamStart, examStart);
    ImplementProtocolString(valueExamEnd, examEnd);
    ImplementProtocolString(valueInfo, info);
    ImplementProtocolString(valueAlert, alert);

    const std::string CExamProtocol::packageKeepAlive = "<package type=\"hold\" />";
    const std::string CExamProtocol::packageKeepAliveReply = "<package type=\"holdReply\" />";

    ImplementProtocolString(packageStart, <package);
    ImplementProtocolString(LabelOneLineEnd,  />);
    ImplementProtocolString(packageEnd, </package>);

    CExamProtocol::CExamProtocol(void)
    {
    }

    inline std::string GenerateAttribute(const std::string &attribute, const std::string &value)
    {
        return " " + attribute + "=\"" + value + "\"";
    }

    inline std::string LabelStart(const std::string &label)
    {
        return "<" + label;
    }

    inline std::string LabelEnd(const std::string &label)
    {
        return "</" + label + ">";
    }

    static const char labelEnd = '>';

    SocketOutputStream & CExamProtocol::SendKeepAlivePackage(SocketOutputStream &stream)
    {
        stream<<packageKeepAlive;
        stream.flush();
        return stream;
    }

    SocketOutputStream & CExamProtocol::SendKeepAliveReplyPackage(SocketOutputStream &stream)
    {
        stream<<packageKeepAliveReply;
        stream.flush();
        return stream;
    }

    SocketOutputStream & CExamProtocol::SendIdentityPackage(SocketOutputStream &stream, Identity identity)
    {
        stream << packageStart
            << GenerateAttribute(attributeType, typeIdentity)
            << GenerateAttribute(attributeIdentity, identity == Server ? valueServer : valueClient)
            << LabelOneLineEnd;
        stream.flush();
        return stream;
    }

    SocketOutputStream & CExamProtocol::SendVersionPackage(SocketOutputStream &stream, VersionType versionType, const std::string &versionValue /*= nullString*/)
    {
        stream << packageStart
            << GenerateAttribute(attributeType, typeVersion)
            << labelEnd;

        stream << LabelStart(labelVersion)
            << GenerateAttribute(attributeType, versionType == SystemVersion ? valueSystem : valueDatabase)
            << GenerateAttribute(attributeValue, versionType == SystemVersion ? systemVersion : versionValue)
            << LabelOneLineEnd;

        stream << packageEnd;

        stream.flush();
        return stream;
    }

    SocketOutputStream & CExamProtocol::SendVersionCheckPackage(SocketOutputStream &stream, bool passed)
    {
        stream <<packageStart
            << GenerateAttribute(attributeType, typeVersionCheck)
            << GenerateAttribute(attributeResult, passed ? valuePassed : valueFailed)
            << LabelOneLineEnd;

        stream.flush();
        return stream;
    }

    SocketOutputStream & CExamProtocol::SendDatabasePackage(SocketOutputStream &stream, const std::string &databaseFile)
    {
        std::ifstream fin;
        fin.open(databaseFile.c_str(), std::ios::binary);
        if (!fin.is_open())
        {
            systemLogger.error("Failed to open file " + databaseFile);
            return stream;
        }

        // get file info
        MD5Engine md5;
        DigestOutputStream dos(md5);
        StreamCopier::copyStream(fin, dos);
        dos.close();
        std::string md5String = DigestEngine::digestToHex(md5.digest());
        fin.seekg(0, fin.end);
        size_t length = fin.tellg();
        char buf[100];
        sprintf_s(buf, "%u", length);

        // start to write
        stream << packageStart
            << GenerateAttribute(attributeType, typeDatabase)
            << labelEnd;

        stream << LabelStart(labelFile)
            << GenerateAttribute(attributeName, databaseFile)
            << GenerateAttribute(attributeSize, buf)
            << GenerateAttribute(attributeMd5, md5String)
            << labelEnd;

        fin.seekg(0, fin.beg);
        Base64Encoder encoder(stream);
        StreamCopier::copyStream(fin, encoder);
        encoder.close();

        stream<<LabelEnd(labelFile);

        stream<<packageEnd;
        stream.flush();
        return stream;
    }

    SocketOutputStream & CExamProtocol::SendReceivingResultPackage(SocketOutputStream &stream, bool passed)
    {
        stream << packageStart
            << GenerateAttribute(attributeType, typeReceivingResult)
            << GenerateAttribute(attributeResult, passed ? valuePassed : valueFailed)
            << LabelOneLineEnd;

        stream.flush();
        return stream;
    }

    SocketOutputStream & CExamProtocol::SendExamOrderPackage(SocketOutputStream &stream, ExamOrder order)
    {
        stream << packageStart
            << GenerateAttribute(attributeType, typeExamOrder)
            << GenerateAttribute(attributeOrder, order == OrderStartExam ? valueExamStart : valueExamEnd)
            << LabelOneLineEnd;
        stream.flush();
        return stream;
    }

    SocketOutputStream & CExamProtocol::SendExamMessagePackage(SocketOutputStream &stream, MessageType type, const std::string &message)
    {
        stream << packageStart
            << GenerateAttribute(attributeType, typeExamMessage)
            << labelEnd;

        stream << LabelStart(labelMessage)
            << GenerateAttribute(attributeType, type == MessageInformation ? valueInfo : valueAlert)
            << labelEnd;

        stream << message;
        stream << LabelEnd(labelMessage);

        stream << packageEnd;
        stream.flush();
        return stream;
    }

    SocketOutputStream & CExamProtocol::SendStreamStartLabel(SocketOutputStream &stream)
    {
        stream << capsuledLabelExamination;
        stream.flush();
        return stream;
    }

    SocketOutputStream & CExamProtocol::SendStreamEndLabel(SocketOutputStream &stream)
    {
        stream << capsuledLabelExaminationEnd;
        stream.flush();
        return stream;
    }

}